/* dot.conf - configuration file parser library
 * Copyright (C) 1999,2000,2001,2002 Lukas Schroeder <lukas@azzit.de>,
 *   and others.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 *
 */

/* -- dotconf.c - this code is responsible for the input, parsing and dispatching of options  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Added by Stephen W. Boyer <sboyer@caldera.com>
 * for wildcard support in Include file paths
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/stat.h>

/* -- AIX 4.3 compile time fix
 * by Eduardo Marcel Macan <macan@colband.com.br>
 *
 * modified by Stephen W. Boyer <sboyer@caldera.com>
 * for Unixware and OpenServer
 */

#if defined (_AIX43) || defined(UNIXWARE) || defined(OSR5)
#include <strings.h>
#endif

#include <stdarg.h>
#include <time.h>
#include <sys/stat.h>

#ifndef WIN32

#include <dirent.h>
#include <unistd.h>

#else /* ndef WIN32 */

#include "readdir.h"			/* WIN32 fix by Robert J. Buck */

#define strncasecmp strnicmp
typedef unsigned long ulong;
#define snprintf _snprintf
#define vsnprintf _vsnprintf
#endif /* !WIN32 */

#include <ctype.h>
#include "./dotconf.h"

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#endif

static char name[CFG_MAX_OPTION + 1];	/* option name */

/*
 * some 'magic' options that are predefined by dot.conf itself for
 * advanced functionality
 */
static DOTCONF_CB(dotconf_cb_include);		/* internal 'Include'     */
static DOTCONF_CB(dotconf_cb_includepath);	/* internal 'IncludePath' */

static configoption_t dotconf_options[] =
{
	{ "Include", ARG_STR, dotconf_cb_include, NULL, CTX_ALL },
	{ "IncludePath", ARG_STR, dotconf_cb_includepath, NULL, CTX_ALL },
	LAST_CONTEXT_OPTION
};

static void skip_whitespace(char **cp, int n, char term)
{
	char *cp1 = *cp;
	while(isspace((int)*cp1) && *cp1 != term && n--)
		cp1++;
	*cp = cp1;
}

static void copy_word(char **dest, char **src, int max, char term)
{
	char *cp1 = *src;
	char *cp2 = *dest;
	while(max-- && !isspace((int)*cp1) && *cp1 != term)
		*cp2++ = *cp1++;
	*cp2 = 0;

	*src = cp1;
	*dest = cp2;
}

static const configoption_t *get_argname_fallback(const configoption_t *options)
{
	int i;

	for (i = 0; (options[i].name && options[i].name[0]); i++);
	if (options[i].type == ARG_NAME && options[i].callback)
		return &options[i];
	return NULL;
}

char *dotconf_substitute_env(configfile_t *configfile, char *str)
{
	char *cp1, *cp2, *cp3, *eos, *eob;
	char *env_value;
	char env_name[CFG_MAX_VALUE + 1];
	char env_default[CFG_MAX_VALUE + 1];
	char tmp_value[CFG_MAX_VALUE + 1];

	memset(env_name, 0, CFG_MAX_VALUE + 1);
	memset(env_default, 0, CFG_MAX_VALUE + 1);
	memset(tmp_value, 0, CFG_MAX_VALUE + 1);

	cp1 = str;
	eob = cp1 + strlen(str) + 1;
	cp2 = tmp_value;
	eos = cp2 + CFG_MAX_VALUE + 1;

	while ((cp1 < eob) && (cp2 < eos) && (*cp1 != '\0'))
	{
		/* substitution needed ?? */
		if (*cp1 == '$' && *(cp1 + 1) == '{')
		{
			cp1 += 2;			/* skip ${ */
			cp3 = env_name;
			while ((cp1 < eob) && !(*cp1 == '}' || *cp1 == ':'))
				*cp3++ = *cp1++;
			*cp3 = '\0';		/* terminate */

			/* default substitution */
			if (*cp1 == ':' && *(cp1 + 1) == '-')
			{
				cp1 += 2;		/* skip :- */
				cp3 = env_default;
				while ((cp1 < eob) && (*cp1 != '}'))
					*cp3++ = *cp1++;
				*cp3 = '\0';	/* terminate */
			}
			else
			{
				while ((cp1 < eob) && (*cp1 != '}'))
					cp1++;
			}

			if (*cp1 != '}')
			{
				dotconf_warning(configfile, DCLOG_WARNING, ERR_PARSE_ERROR,
					"Unbalanced '{'");
			}
			else
			{
				cp1++;			/* skip } */
				if ((env_value = getenv(env_name)) != NULL)
				{
					strncat(cp2, env_value, eos - cp2);
					cp2 += strlen(env_value);
				}
				else
				{
					strncat(cp2, env_default, eos - cp2);
					cp2 += strlen(env_default);
				}
			}

		}

		*cp2++ = *cp1++;
	}
	*cp2 = '\0';				/* terminate buffer */

	free(str);
	return strdup(tmp_value);
}

int dotconf_warning(configfile_t *configfile, int type, unsigned long errnum, const char *fmt, ...)
{
	va_list args;
	int retval = 0;

	va_start(args, fmt);
	if (configfile->errorhandler != 0)	/* an errorhandler is registered */
	{
		char msg[CFG_BUFSIZE];
		vsnprintf(msg, CFG_BUFSIZE, fmt, args);
		retval = configfile->errorhandler(configfile, type, errnum, msg);
	}
	else						/* no errorhandler, do-it-yourself */
	{
		retval = 0;
		fprintf(stderr, "%s:%ld: ", configfile->filename, configfile->line);
		vfprintf(stderr, fmt, args);
		fprintf(stderr, "\n");
	}
	va_end(args);

	return retval;
}

void dotconf_register_options(configfile_t *configfile, const configoption_t * options)
{
	int num = configfile->config_option_count;

#define GROW_BY   10

	/* resize memoryblock for options blockwise */
	if (configfile->config_options == NULL)
		configfile->config_options = malloc(sizeof(void *) * (GROW_BY + 1));
	else
	{
		if ( !(num % GROW_BY) )
			configfile->config_options = realloc(configfile->config_options,
											 sizeof(void *) * (num + GROW_BY + 1));
	}

#undef GROW_BY

	/* append new options */
	configfile->config_options[configfile->config_option_count] = options;
	configfile->config_options[ ++configfile->config_option_count ] = 0;

}

void dotconf_callback(configfile_t *configfile, callback_types type, dotconf_callback_t callback)
{
	switch(type)
	{
		case ERROR_HANDLER:
			configfile->errorhandler = (dotconf_errorhandler_t) callback;
			break;
		case CONTEXT_CHECKER:
			configfile->contextchecker = (dotconf_contextchecker_t) callback;
			break;
		default:
			break;
	}
}

int dotconf_continue_line(char *buffer, size_t length)
{
	/* ------ match [^\\]\\[\r]\n ------------------------------ */
	char *cp1 = buffer + length - 1;

	if (length < 2)
		return 0;

	if (*cp1-- != '\n')
		return 0;

	if (*cp1 == '\r')
		cp1--;

	if (*cp1-- != '\\')
		return 0;

	cp1[1] = 0;						/* strip escape character and/or newline */
	return (*cp1 != '\\');
}

int dotconf_get_next_line(char *buffer, size_t bufsize, configfile_t *configfile)
{
	char *cp1, *cp2;
	char buf2[CFG_BUFSIZE];
	int length;

	if (configfile->eof)
		return 1;

	cp1 = fgets(buffer, CFG_BUFSIZE, configfile->stream);

	if (!cp1)
	{
		configfile->eof = 1;
		return 1;
	}

	configfile->line++;
	length = strlen(cp1);
	while ( dotconf_continue_line(cp1, length) )
	{
		cp2 = fgets(buf2, CFG_BUFSIZE, configfile->stream);
		if (!cp2)
		{
			fprintf(stderr, "[dotconf] Parse error. Unexpected end of file at "
					"line %ld in file %s\n", configfile->line, configfile->filename);
			configfile->eof = 1;
			return 1;
		}
		configfile->line++;
		strcpy(cp1 + length - 2, cp2);
		length = strlen(cp1);
	}

	return 0;
}

char *dotconf_get_here_document(configfile_t *configfile, const char *delimit)
{
	/* it's a here-document: yeah, what a cool feature ;) */
	unsigned int limit_len;
	char here_string;
	char buffer[CFG_BUFSIZE];
	char *here_doc = 0;
	char here_limit[9];	                       /* max length for here-document delimiter: 8 */
	struct stat finfo;
	int offset = 0;

	if (configfile->size <= 0)
	{
		if (stat(configfile->filename, &finfo))
		{
			dotconf_warning(configfile, DCLOG_EMERG, ERR_NOACCESS,
						   "[emerg] could not stat currently read file (%s)\n",
							configfile->filename);
			return NULL;
		}
		configfile->size = finfo.st_size;
	}

	/*
	 * allocate a buffer of filesize bytes; should be enough to
	 * prevent buffer overflows
	 */
	here_doc = malloc(configfile->size);	/* allocate buffer memory */
	memset(here_doc, 0, configfile->size);

	here_string = 1;
	limit_len = snprintf(here_limit, 9, "%s", delimit);
	while (!dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile))
	{
		if (!strncmp(here_limit, buffer, limit_len - 1))
		{
			here_string = 0;
			break;
		}
		offset += snprintf( (here_doc + offset), configfile->size - offset - 1, "%s", buffer);
	}
	if (here_string)
		dotconf_warning(configfile, DCLOG_WARNING, ERR_PARSE_ERROR, "Unterminated here-document!");

	here_doc[offset-1] = '\0';	/* strip newline */

	return (char *)realloc(here_doc, offset);
}

const char *dotconf_invoke_command(configfile_t *configfile, command_t *cmd)
{
	const char *error = 0;

	error = cmd->option->callback(cmd, configfile->context);
	return error;
}

char *dotconf_read_arg(configfile_t *configfile, char **line)
{
	int sq = 0, dq = 0;							/* single quote, double quote */
	int done;
	char *cp1 = *line;
	char *cp2, *eos;
	char buf[CFG_MAX_VALUE];

	memset(buf, 0, CFG_MAX_VALUE);
	done = 0;
	cp2 = buf;
	eos = cp2 + CFG_MAX_VALUE - 1;

	if (*cp1 == '#' || !*cp1)
		return NULL;

	skip_whitespace(&cp1, CFG_MAX_VALUE, 0);

	while ((*cp1 != '\0') && (cp2 != eos) && !done) {
		switch (*cp1) {
			case '\'':					/* single quote */
				if (dq)
					break;					/* already double quoting, break out */
				if (sq)
					sq--;					/* already single quoting, clear state */
				else if (!sq)
					sq++;					/* set state for single quoting */
				break;
			case '"':					/* double quote */
				if (sq)
					break;					/* already single quoting, break out */
				if (dq)
					dq--;					/* already double quoting, clear state */
				else if (!dq)
					dq++;					/* set state for double quoting */
				break;
			case '\\':					/* protected chars */
				if (!cp1[1])			/* dont protect NUL */
					break;
				*cp2++ = *(++cp1);
				cp1++;					/* skip the protected one */
				continue;
				break;
			default:
				break;
		}

		/* unquoted space: start a new option argument */
		if (isspace((int)*cp1) && !dq && !sq) {
			*cp2 = '\0';
			break;
		}
		/* unquoted, unescaped comment-hash ; break out, unless NO_INLINE_COMMENTS is set */
		else if (*cp1 == '#' && !dq && !sq && !(configfile->flags & NO_INLINE_COMMENTS)) {
			/* 
			 * NOTE: 1.0.8a got the NO_INLINE_COMMENTS feature wrong: it
             * skipped every argument starting with a #, instead of simply eating it!
			 */

			*cp2 = 0;
			*cp1 = 0;
			*line = cp1;
			return NULL;
		}
		/* not space or quoted: eat it; dont take quote if quoting */
		else if ( (!isspace((int)*cp1) && !dq && !sq && *cp1 != '"' && *cp1 != '\'')
				   || (dq && (*cp1 != '"')) || (sq && *cp1 != '\'') ) {
			*cp2++ = *cp1;
		}

		cp1++;
	}

	*line = cp1;

	/* FIXME: escaping substitutes does not work
		Subst ${HOME} \$\{HOME\}
		BOTH! will be substituted, which is somewhat wrong, ain't it ?? :-(
	*/
	if ( (configfile->flags & DONT_SUBSTITUTE) == DONT_SUBSTITUTE )
		return buf[0] ? strdup(buf) : NULL;
 	return buf[0] ? dotconf_substitute_env(configfile, strdup(buf)) : NULL;
}

/* dotconf_find_command remains here for backwards compatability. it's
 * internally unused since dot.conf 1.0.9 because it cannot handle the
 * DUPLICATE_OPTION_NAMES flag
 */
configoption_t *dotconf_find_command(configfile_t *configfile, const char *command)
{
	configoption_t *option;
	int i = 0, mod = 0, done = 0;

	for (option = 0, mod = 0; configfile->config_options[mod] && !done; mod++)
		for (i = 0; configfile->config_options[mod][i].name[0]; i++)
		{
			if (!configfile->cmp_func(name, 
									  configfile->config_options[mod][i].name, CFG_MAX_OPTION))
			{
				option = (configoption_t *) &configfile->config_options[mod][i];
				/* TODO: this could be flagged: option overwriting by modules */
				done = 1;
				break;		/* found it; break out */
			}
		}

	/* handle ARG_NAME fallback */
	if ( (option && option->name[0] == 0) 
		  || configfile->config_options[mod - 1][i].type == ARG_NAME)
	{
		option = (configoption_t *) &configfile->config_options[mod - 1][i];
	}

	return option;
}

void dotconf_set_command(configfile_t *configfile, const configoption_t *option, char *args, command_t *cmd)
{
	char *eob = 0;

	eob = args + strlen(args);

	/* fill in the command_t structure with values we already know */
	cmd->name = option->type == ARG_NAME ? name : option->name;
	cmd->option = (configoption_t *)option;
	cmd->context = configfile->context;
	cmd->configfile = configfile;
	cmd->data.list = (char **)calloc(CFG_VALUES, sizeof(char *));
	cmd->data.str = 0;

	if (option->type == ARG_RAW) {
		/* if it is an ARG_RAW type, save some time and call the
		   callback now */
		cmd->data.str = strdup(args);
	}
	else if (option->type == ARG_STR) {
		char *cp = args;

		/* check if it's a here-document and act accordingly */
		skip_whitespace(&cp, (long)eob - (long)cp, 0);

		if (!strncmp("<<", cp, 2)) {
			cmd->data.str = dotconf_get_here_document(configfile, cp + 2);
			cmd->arg_count = 1;
		}
	}

	if (!(option->type == ARG_STR && cmd->data.str != 0)) {
		/* we only get here for non-heredocument lines */

		skip_whitespace(&args, eob - args, 0);

		cmd->arg_count = 0;
		while ( cmd->arg_count < (CFG_VALUES - 1)
				&& (cmd->data.list[cmd->arg_count] = dotconf_read_arg(configfile, &args)) ) {
			cmd->arg_count++;
		}

		skip_whitespace(&args, eob - args, 0);

		if (cmd->arg_count && cmd->data.list[cmd->arg_count-1] && *args)
			cmd->data.list[cmd->arg_count++] = strdup(args);

		/* has an option entry been found before or do we have to use a fallback? */
		if ((option->name && option->name[0] > 32) || option->type == ARG_NAME) {
			/* found it, now check the type of args it wants */
			switch (option->type) {
				case ARG_TOGGLE:
					/* the value is true if the argument is Yes, On or 1 */
					if (cmd->arg_count < 1) {
						dotconf_warning(configfile, DCLOG_WARNING, ERR_WRONG_ARG_COUNT,
										"Missing argument to option '%s'", name);
						return;
					}

					cmd->data.value = CFG_TOGGLED(cmd->data.list[0]);
					break;
				case ARG_INT:
					if (cmd->arg_count < 1) {
						dotconf_warning(configfile, DCLOG_WARNING, ERR_WRONG_ARG_COUNT,
										"Missing argument to option '%s'", name);
						return;
					}

					sscanf(cmd->data.list[0], "%li", &cmd->data.value);
					break;
				case ARG_STR:
					if (cmd->arg_count < 1) {
						dotconf_warning(configfile, DCLOG_WARNING, ERR_WRONG_ARG_COUNT,
										"Missing argument to option '%s'", name);
						return;
					}

					cmd->data.str = strdup(cmd->data.list[0]);
					break;
				case ARG_NAME:	/* fall through */
				case ARG_LIST:
				case ARG_NONE:
				case ARG_RAW:	/* this has been handled before */
				default:
					break;
			}
		}
	}
}

void dotconf_free_command(command_t *command)
{
	int i;

	if (command->data.str)
		free(command->data.str);

	for (i = 0; i < command->arg_count; i++)
		free(command->data.list[i]);
	free(command->data.list);
}

const char *dotconf_handle_command(configfile_t *configfile, char *buffer)
{
	char *cp1; 
	char *cp2;
	/* generic char pointer      */
	char *eob;									/* end of buffer; end of string  */
	const char *error;							/* error message we'll return */
	const char *context_error;					/* error message returned by contextchecker */
	command_t command;							/* command structure */
	int mod = 0;
	int next_opt_idx = 0;

	memset(&command, 0, sizeof(command_t));
	name[0] = 0;
	error = 0;
	context_error = 0;

	cp1 = buffer;
	eob = cp1 + strlen(cp1);

	skip_whitespace(&cp1, (long)eob - (long)cp1, 0);

	/* ignore comments and empty lines */
	if (!cp1 || !*cp1 || *cp1 == '#' || *cp1 == '\n' || *cp1 == (char)EOF)
		return NULL;

	/* skip line if it only contains whitespace */
	if (cp1 == eob)
		return NULL;

	/* get first token: read the name of a possible option */
	cp2 = name;
	copy_word(&cp2, &cp1, MIN(eob - cp1, CFG_MAX_OPTION), 0);

	while (1) {
		const configoption_t *option;
		int done = 0;
		int opt_idx = 0;

		for (option = 0; configfile->config_options[mod] && !done; mod++) {
			for (opt_idx = next_opt_idx; configfile->config_options[mod][opt_idx].name[0]; opt_idx++) {
				if (!configfile->cmp_func(name, configfile->config_options[mod][opt_idx].name, CFG_MAX_OPTION)) {
					/* TODO: this could be flagged: option overwriting by modules */
					option = (configoption_t *) &configfile->config_options[mod][opt_idx];
					done = 1;
					break;		/* found one; break out */
				}
			}
		}

		if (!option)
			option = get_argname_fallback(configfile->config_options[1]);
		
		if (!option || !option->callback) {
			if (error)
				return error;
			dotconf_warning(configfile, DCLOG_INFO, ERR_UNKNOWN_OPTION,
							"Unknown Config-Option: '%s'", name);
			return NULL;
		}

		/* set up the command structure (contextchecker wants this) */
		dotconf_set_command(configfile, option, cp1, &command);

		if (configfile->contextchecker)
			context_error = configfile->contextchecker(&command, command.option->context);

		if (!context_error) 
			error = dotconf_invoke_command(configfile, &command);
		else {
			if (!error) {
				/* avoid returning another error then the first. This makes it easier to
                   reproduce problems. */
				error = context_error;
			}
		}

		dotconf_free_command(&command);

		if (!context_error || !(configfile->flags & DUPLICATE_OPTION_NAMES)) {
			/* don't try more, just quit now. */
			break;
		}
	}

	return error;
}

const char *dotconf_command_loop_until_error(configfile_t *configfile)
{
	char buffer[CFG_BUFSIZE];

	while ( !(dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile)) )
	{
		const char *error = dotconf_handle_command(configfile, buffer);
		if ( error )
			return error;
	}
	return NULL;
}

int dotconf_command_loop(configfile_t *configfile)
{
	/* ------ returns: 0 for failure -- !0 for success ------------------------------------------ */
	char buffer[CFG_BUFSIZE];

	while ( !(dotconf_get_next_line(buffer, CFG_BUFSIZE, configfile)) )
	{
		const char *error = dotconf_handle_command(configfile, buffer);
		if ( error != NULL )
		{
			if ( dotconf_warning(configfile, DCLOG_ERR, 0, error) )
				return 0;
		}
	}
	return 1;
}

configfile_t *dotconf_create(char *fname, const configoption_t * options,
                             context_t *context, unsigned long flags)
{
	configfile_t *new = 0;
	char *dc_env;

	if (access(fname, R_OK))
	{
		fprintf(stderr, "Error opening configuration file '%s'\n", fname);
		return NULL;
	}

	new = calloc(1, sizeof(configfile_t));
	if (!(new->stream = fopen(fname, "r")))
	{
		fprintf(stderr, "Error opening configuration file '%s'\n", fname);
		free(new);
		return NULL;
	}

	new->flags = flags;
	new->filename = strdup(fname);

	new->includepath = malloc(CFG_MAX_FILENAME);
	new->includepath[0] = 0x00;

	/* take includepath from environment if present */
	if ((dc_env = getenv(CFG_INCLUDEPATH_ENV)) != NULL)
		snprintf(new->includepath, CFG_MAX_FILENAME, "%s", dc_env);

	new->context = context;

	dotconf_register_options(new, dotconf_options);
	dotconf_register_options(new, options);

	if ( new->flags & CASE_INSENSITIVE )
		new->cmp_func = strncasecmp;
	else
		new->cmp_func = strncmp;

	return new;
}

void dotconf_cleanup(configfile_t *configfile)
{
	if (configfile->stream)
		fclose(configfile->stream);

	if (configfile->filename)
		free(configfile->filename);

	if (configfile->config_options)
		free(configfile->config_options);

	if (configfile->includepath)
		free(configfile->includepath);

	free(configfile);
}

/* ------ internal utility function that verifies if a character is in the WILDCARDS list -- */
int dotconf_is_wild_card(char value)
{
	int retval = 0;
	int i;
	int wildcards_len = strlen(WILDCARDS);

	for (i=0;i<wildcards_len;i++)
	{
		if (value == WILDCARDS[i])
		{
			retval = 1;
			break;
		}
	}

	return retval;
}

/* ------ internal utility function that calls the appropriate routine for the wildcard passed in -- */
int dotconf_handle_wild_card(command_t* cmd, char wild_card, char* path, char* pre, char* ext)
{
	int retval = 0;

	switch (wild_card)
	{
		case '*':

			retval = dotconf_handle_star(cmd,path,pre,ext);

		break;

		case '?':

			retval = dotconf_handle_question_mark(cmd,path,pre,ext);

		break;

		default:
			retval = -1;
	}

	return retval;
}


/* ------ internal utility function that frees allocated memory from dotcont_find_wild_card -- */
void dotconf_wild_card_cleanup(char* path, char* pre)
{

	if (path != NULL)
	{
		free(path);
	}

	if (pre != NULL)
	{
		free(pre);
	}

}

/* ------ internal utility function to check for wild cards in file path -- */
/* ------ path and pre must be freed by the developer ( dotconf_wild_card_cleanup) -- */
int dotconf_find_wild_card(char* filename, char* wildcard, char** path, char** pre, char** ext)
{
	int retval = -1;
	int prefix_len = 0;
	int tmp_count = 0;
	char* tmp = 0;
	int found_path = 0;

	int len = strlen(filename);

	if (wildcard != NULL && len > 0 && path != NULL && pre != NULL && ext != NULL )
	{
		prefix_len = strcspn(filename,WILDCARDS); /* find any wildcard in WILDCARDS */

		if ( prefix_len < len ) /* Wild card found */
		{
			tmp = filename + prefix_len;
			tmp_count = prefix_len + 1;

			while ( tmp != filename && *(tmp) != '/' )
			{
				tmp--;
				tmp_count--;
			}

			if ( *(tmp) == '/' )
			{
				*path = (char*)malloc(tmp_count+1);
				found_path = 1;

			} else

				*path = (char*)malloc(1);

			*pre =  (char*)malloc((prefix_len-(tmp_count-(found_path?0:1)))+1);

			if ( *path && *pre )
			{
				if (found_path)
					strncpy(*path,filename,tmp_count);
				(*path)[tmp_count] = '\0';

				strncpy(*pre,(tmp+(found_path?1:0)),
						(prefix_len-(tmp_count-(found_path?0:1))));
				(*pre)[(prefix_len-(tmp_count-(found_path?0:1)))] = '\0';

				*ext = filename + prefix_len;
				*wildcard = (**ext);
				(*ext)++;

				retval = prefix_len;

			}

		}

	}

	return retval;
}

/* ------ internal utility function that compares two stings from back to front -- */
int dotconf_strcmp_from_back(const char* s1, const char* s2)
{
	int retval = 0;
	int i,j;
	int len_1 = strlen(s1);
	int len_2 = strlen(s2);

	for (i=len_1,j=len_2;(i>=0 && j>=0);i--,j--)
	{
		if (s1[i] != s2[j])
		{
			retval = -1;
			break;
		}
	}

	return retval;
}

/* ------ internal utility function that determins if a string matches the '?' criteria -- */
int dotconf_question_mark_match(char* dir_name, char* pre, char* ext)
{
	int retval = -1;
	int dir_name_len = strlen(dir_name);
	int pre_len = strlen(pre);
	int ext_len = strlen(ext);
	int w_card_check = strcspn(ext,WILDCARDS);

	if ( (w_card_check < ext_len) && (strncmp(dir_name,pre,pre_len) == 0)
		 && (strcmp(dir_name,".") != 0 ) && (strcmp(dir_name,"..") != 0) )
	{
		retval = 1;    /* Another wildcard found */

	} else {

		if ((dir_name_len >= pre_len) &&
			 (strncmp(dir_name,pre,pre_len) == 0) &&
			 (strcmp(dir_name,".") != 0 ) &&
			 (strcmp(dir_name,"..") != 0))
		{
			retval = 0; /* Matches no other wildcards */
		}

	}

	return retval;
}

/* ------ internal utility function that determins if a string matches the '*' criteria -- */
int dotconf_star_match(char* dir_name, char* pre, char* ext)
{
	int retval = -1;
	int dir_name_len = strlen(dir_name);
	int pre_len = strlen(pre);
	int ext_len = strlen(ext);
	int w_card_check = strcspn(ext,WILDCARDS);

	if ( (w_card_check < ext_len) && (strncmp(dir_name,pre,pre_len) == 0)
		 && (strcmp(dir_name,".") != 0 ) && (strcmp(dir_name,"..") != 0) )
	{
		retval = 1;    /* Another wildcard found */

	} else {

		if ((dir_name_len >= (ext_len + pre_len)) &&
			 (dotconf_strcmp_from_back(dir_name,ext) == 0) &&
			 (strncmp(dir_name,pre,pre_len) == 0) &&
			 (strcmp(dir_name,".") != 0 ) &&
			 (strcmp(dir_name,"..") != 0))
		{
			retval = 0; /* Matches no other wildcards */
		}

	}

	return retval;
}

/* ------ internal utility function that determins matches for filenames with   -- */
/* ------ a '?' in name and calls the Internal Include function on that filename -- */
int dotconf_handle_question_mark(command_t* cmd, char* path, char* pre, char* ext)
{
	configfile_t *included;
	DIR* dh = 0;
	struct dirent* dirptr = 0;
	int i;

	char new_pre[CFG_MAX_FILENAME];
	char already_matched[CFG_MAX_FILENAME];

	char wc = '\0';

	char* new_path = 0;
	char* wc_path = 0;
	char* wc_pre = 0;
	char* wc_ext = 0;

	int pre_len;
	int new_path_len;
	int name_len = 0;
	int alloced = 0;
	int match_state = 0;

	pre_len = strlen(pre);

	if ((dh = opendir(path)) != NULL)
	{
		while ( (dirptr = readdir(dh)) != NULL )
		{
			match_state = dotconf_question_mark_match(dirptr->d_name,pre,ext);

			if (match_state >= 0)
			{
				name_len = strlen(dirptr->d_name);
				new_path_len = strlen(path) + name_len + strlen(ext) + 1;

				if ( !alloced )
				{
					if ((new_path = (char*)malloc(new_path_len)) == NULL )
					{
						return -1;
					}

					alloced = new_path_len;

				} else {

						if ( new_path_len > alloced )
						{
							if ( realloc(new_path,new_path_len) == NULL )
							{
								free(new_path);
								return -1;
							}

						}

				}

				if (match_state == 1)
				{

					strncpy(new_pre,dirptr->d_name,(name_len > pre_len)?(pre_len+1):pre_len);
					new_pre[(name_len > pre_len)?(pre_len+1):pre_len] = '\0';

					sprintf(new_path,"%s%s%s",path,new_pre,ext);

					if (strcmp(new_path,already_matched) == 0)
					{
						continue; /* Already searched this expression */

					} else {

						strcpy(already_matched,new_path);

					}

					if (dotconf_find_wild_card(new_path,&wc,&wc_path,&wc_pre,&wc_ext) >= 0)
					{
						if ( dotconf_handle_wild_card(cmd,wc,wc_path,wc_pre,wc_ext) < 0)
						{
							dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
											"Error occured while processing wildcard %c\n"
											"Filename is '%s'\n", wc, new_path);

							free(new_path);
							dotconf_wild_card_cleanup(wc_path,wc_pre);
							return -1;
						}

						dotconf_wild_card_cleanup(wc_path,wc_pre);
						continue;
					}

				}
#ifdef HAVE_SNPRINTF
				snprintf(new_path, new_path_len, "%s%s", path, dirptr->d_name);
#else
				sprintf(new_path,"%s%s",path,dirptr->d_name);
#endif
				if (access(new_path, R_OK))
				{
					dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
									"Cannot open %s for inclusion.\n"
									"IncludePath is '%s'\n", new_path, cmd->configfile->includepath);
					return -1;
				}

				included = dotconf_create(new_path, cmd->configfile->config_options[1],
											cmd->configfile->context, cmd->configfile->flags);
				if (included)
				{
					for (i = 2; cmd->configfile->config_options[i]; i++)
						dotconf_register_options(included, cmd->configfile->config_options[i]);
					included->errorhandler = cmd->configfile->errorhandler;
					included->contextchecker = cmd->configfile->contextchecker;
					dotconf_command_loop(included);
					dotconf_cleanup(included);
				}

			}

		}

		closedir(dh);
		free(new_path);

	}

	return 0;
}

/* ------ internal utility function that determins matches for filenames with   --- */
/* ------ a '*' in name and calls the Internal Include function on that filename -- */
int dotconf_handle_star(command_t* cmd, char* path, char* pre, char* ext)
{
	configfile_t *included;
	DIR* dh = 0;
	struct dirent* dirptr = 0;

	char new_pre[CFG_MAX_FILENAME];
	char new_ext[CFG_MAX_FILENAME];
	char already_matched[CFG_MAX_FILENAME];

	char wc = '\0';

	char* new_path = 0;
	char* s_ext = 0;
	char* t_ext = 0;
	char* sub = 0;
	char* wc_path = 0;
	char* wc_pre = 0;
	char* wc_ext = 0;

	int pre_len;
	int new_path_len;
	int name_len = 0;
	int alloced = 0;
	int match_state = 0;
	int t_ext_count = 0;
	int sub_count = 0;

	pre_len = strlen(pre);
	memset(already_matched,0,CFG_MAX_FILENAME);
	s_ext = ext;

	while (dotconf_is_wild_card(*s_ext)) /* remove trailing wild-cards proceeded by * */
	{
		s_ext++;
	}

	t_ext = s_ext;

	while(t_ext != NULL && !(dotconf_is_wild_card(*t_ext)) && *t_ext != '\0')
	{
		t_ext++;				/* find non-wild-card string */
		t_ext_count++;
	}

	strncpy(new_ext,s_ext,t_ext_count);
	new_ext[t_ext_count] = '\0';

	if ((dh = opendir(path)) != NULL)
	{
		while ( (dirptr = readdir(dh)) != NULL )
		{
			sub_count = 0;
			t_ext_count = 0;

			match_state = dotconf_star_match(dirptr->d_name,pre,s_ext);

			if (match_state >= 0)
			{
				name_len = strlen(dirptr->d_name);
				new_path_len = strlen(path) + name_len + strlen(s_ext) + 1;

				if ( !alloced )
				{
					if ((new_path = (char*)malloc(new_path_len)) == NULL )
					{
						return -1;
					}

					alloced = new_path_len;

				} else {

						if ( new_path_len > alloced )
						{
							if ( realloc(new_path,new_path_len) == NULL )
							{
								free(new_path);
								return -1;
							}

						}

				}

				if (match_state == 1)
				{

					if ((sub = strstr((dirptr->d_name+pre_len),new_ext)) == NULL)
					{
						continue;
					}

					while (sub != dirptr->d_name)
					{
						sub--;
						sub_count++;
					}

					if (sub_count + t_ext_count > name_len)
					{
						continue;
					}

					strncpy(new_pre,dirptr->d_name,(sub_count+t_ext_count));
					new_pre[sub_count+t_ext_count] = '\0';
#ifdef HAVE_STRLCAT
  					strlcat(new_pre,new_ext,CFG_MAX_FILENAME);
#else
					strcat(new_pre,new_ext);
#endif

					sprintf(new_path,"%s%s%s",path,new_pre,t_ext);

					if (strcmp(new_path,already_matched) == 0)
					{
						continue; /* Already searched this expression */

					} else {

						strcpy(already_matched,new_path);

					}

					if (dotconf_find_wild_card(new_path,&wc,&wc_path,&wc_pre,&wc_ext) >= 0)
					{
						if ( dotconf_handle_wild_card(cmd,wc,wc_path,wc_pre,wc_ext) < 0)
						{
							dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
											"Error occured while processing wildcard %c\n"
											"Filename is '%s'\n", wc, new_path);

							free(new_path);
							dotconf_wild_card_cleanup(wc_path,wc_pre);
							return -1;
						}

						dotconf_wild_card_cleanup(wc_path,wc_pre);
						continue;
					}

				}

				sprintf(new_path,"%s%s",path,dirptr->d_name);

				if (access(new_path, R_OK))
				{
					dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
									"Cannot open %s for inclusion.\n"
									"IncludePath is '%s'\n", new_path, cmd->configfile->includepath);
					return -1;
				}

				included = dotconf_create(new_path, cmd->configfile->config_options[1],
											cmd->configfile->context, cmd->configfile->flags);
				if (included)
				{
					included->errorhandler = cmd->configfile->errorhandler;
					included->contextchecker = cmd->configfile->contextchecker;
					dotconf_command_loop(included);
					dotconf_cleanup(included);
				}

			}

		}

		closedir(dh);
		free(new_path);

	}

	return 0;
}

/* ------ callbacks of the internal option (Include, IncludePath) ------------------------------- */
DOTCONF_CB(dotconf_cb_include)
{
	char *filename = 0;
	configfile_t *included;

	char wild_card;
	char* path = 0;
	char* pre = 0;
	char* ext = 0;


	if (cmd->configfile->includepath
		&& cmd->data.str[0] != '/' && cmd->configfile->includepath[0] != '\0')
	{
		/* relative file AND include path is used */
		int len, inclen;
		char *sl;

		inclen = strlen(cmd->configfile->includepath);
		if (( len = (strlen(cmd->data.str) + inclen + 1)) == CFG_MAX_FILENAME)
		{
			dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
							"Absolute filename too long (>%d)", CFG_MAX_FILENAME);
			return NULL;
		}

		if (cmd->configfile->includepath[inclen - 1] == '/')
			sl = "";
		else
		{
			sl = "/";
			len++;
		}

		filename = malloc(len);
		snprintf(filename, len, "%s%s%s",
				 cmd->configfile->includepath, sl, cmd->data.str);
	}
	else						/* fully qualified, or no includepath */
		filename = strdup(cmd->data.str);

	/* Added wild card support here */
	if (dotconf_find_wild_card(filename,&wild_card,&path,&pre,&ext) >= 0)
	{
		if ( dotconf_handle_wild_card(cmd,wild_card,path,pre,ext) < 0)
		{
			dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
							"Error occured while attempting to process %s for inclusion.\n"
							"IncludePath is '%s'\n", filename, cmd->configfile->includepath);
		}

		dotconf_wild_card_cleanup(path,pre);
		free(filename);
		return NULL;
	}

	if (access(filename, R_OK))
	{
		dotconf_warning(cmd->configfile, DCLOG_WARNING, ERR_INCLUDE_ERROR,
						"Cannot open %s for inclusion.\n"
						"IncludePath is '%s'\n", filename, cmd->configfile->includepath);
		free(filename);
		return NULL;
	}

	included = dotconf_create(filename, cmd->configfile->config_options[1],
							   cmd->configfile->context, cmd->configfile->flags);
	if (included)
	{
		included->contextchecker = (dotconf_contextchecker_t) cmd->configfile->contextchecker;
		included->errorhandler = (dotconf_errorhandler_t) cmd->configfile->errorhandler;

		dotconf_command_loop(included);
		dotconf_cleanup(included);
	}

	free(filename);
	return NULL;
}

DOTCONF_CB(dotconf_cb_includepath)
{
	char *env = getenv(CFG_INCLUDEPATH_ENV);
	/* environment overrides configuration file setting */
	if (!env)
		snprintf(cmd->configfile->includepath, CFG_MAX_FILENAME, "%s", cmd->data.str);
	return NULL;
}

/* vim:set ts=4: */
